/* ------------------------------------------------------------------------ */
/* LHa for UNIX                                                             */
/*				header.c -- header manipulate functions						*/
/*																			*/
/*		Modified                Nobutaka Watazaki							*/
/*																			*/
/*	Original												Y.Tagawa		*/
/*	modified									1991.12.16	M.Oki			*/
/*	Ver. 1.10  Symbolic Link added				1993.10.01	N.Watazaki		*/
/*	Ver. 1.13b Symbolic Link Bug Fix			1994.08.22	N.Watazaki		*/
/*	Ver. 1.14  Source All chagned				1995.01.14	N.Watazaki		*/
/*  Ver. 1.14i bug fixed						2000.10.06  t.okamoto       */
/* ------------------------------------------------------------------------ */

#include "sysconfig.h"
#include "lha_macro.h"
#include "lha.h"
#include "zfile.h"

#define FTIME
#define MKTIME

/* ------------------------------------------------------------------------ */
static char* get_ptr;
/* ------------------------------------------------------------------------ */
int calc_sum(char* p, int len)
{
    register int sum;

    for (sum = 0; len; len--)
        sum += *p++;

    return sum & 0xff;
}

/* ------------------------------------------------------------------------ */
static unsigned short get_word()
{
    int b0, b1;

    b0 = get_byte();
    b1 = get_byte();
    return (b1 << 8) + b0;
}

/* ------------------------------------------------------------------------ */
static void put_word(uint v)
{
    put_byte(v);
    put_byte(v >> 8);
}

/* ------------------------------------------------------------------------ */
static long get_longword()
{
    long b0, b1, b2, b3;

    b0 = get_byte();
    b1 = get_byte();
    b2 = get_byte();
    b3 = get_byte();
    return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
}

/* ------------------------------------------------------------------------ */
static void put_longword(long v)
{
    put_byte(v);
    put_byte(v >> 8);
    put_byte(v >> 16);
    put_byte(v >> 24);
}

/* ------------------------------------------------------------------------ */
static void msdos_to_unix_filename(char* name, int len)
{
    register int i;

    #ifdef MULTIBYTE_CHAR
    for (i = 0; i < len; i++)
    {
        if (MULTIBYTE_FIRST_P(name[i]) &&
            MULTIBYTE_SECOND_P(name[i + 1]))
            i++;
        else if (name[i] == '\\')
            name[i] = '/';
        else if (!noconvertcase && isupper(name[i]))
            name[i] = tolower(name[i]);
    }
    #else
    for (i = 0; i < len; i++)
    {
        if (name[i] == '\\')
            name[i] = '/';
        else if (!noconvertcase && isupper(name[i]))
            name[i] = tolower(name[i]);
    }
    #endif
}

/* ------------------------------------------------------------------------ */
static void generic_to_unix_filename(char* name, int len)
{
    register int i;
    boolean lower_case_used = FALSE;

    #ifdef MULTIBYTE_CHAR
    for (i = 0; i < len; i++)
    {
        if (MULTIBYTE_FIRST_P(name[i]) &&
            MULTIBYTE_SECOND_P(name[i + 1]))
            i++;
        else if (islower(name[i]))
        {
            lower_case_used = TRUE;
            break;
        }
    }
    for (i = 0; i < len; i++)
    {
        if (MULTIBYTE_FIRST_P(name[i]) &&
            MULTIBYTE_SECOND_P(name[i + 1]))
            i++;
        else if (name[i] == '\\')
            name[i] = '/';
        else if (!noconvertcase && !lower_case_used && isupper(name[i]))
            name[i] = tolower(name[i]);
    }
    #else
    for (i = 0; i < len; i++)
        if (islower(name[i]))
        {
            lower_case_used = TRUE;
            break;
        }
    for (i = 0; i < len; i++)
    {
        if (name[i] == '\\')
            name[i] = '/';
        else if (!noconvertcase && !lower_case_used && isupper(name[i]))
            name[i] = tolower(name[i]);
    }
    #endif
}

/* ------------------------------------------------------------------------ */
static void
macos_to_unix_filename(char* name, int len)
{
    register int i;

    for (i = 0; i < len; i++)
    {
        if (name[i] == ':')
            name[i] = '/';
        else if (name[i] == '/')
            name[i] = ':';
    }
}

/* ------------------------------------------------------------------------ */
static void
unix_to_generic_filename(char* name, int len)
{
    register int i;

    for (i = 0; i < len; i++)
    {
        if (name[i] == '/')
            name[i] = '\\';
        else if (islower(name[i]))
            name[i] = toupper(name[i]);
    }
}

/* ------------------------------------------------------------------------ */
/*																			*/
/* Generic stamp format:						                            */
/*																			*/
/* 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16							*/
/* |<-------- year ------->|<- month ->|<-- day -->|						*/
/*																			*/
/* 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0							*/
/* |<--- hour --->|<---- minute --->|<- second*2 ->|						*/
/*																			*/
/* ------------------------------------------------------------------------ */

/*
 * NOTE : If you don't have `gettimeofday(2)', or your gettimeofday(2)
 * returns bogus timezone information, try FTIME, MKTIME, TIMELOCAL or TZSET.
 */

/* choose one */
#if defined (MKTIME)
    #ifdef TIMELOCAL
        #undef TIMELOCAL
    #endif
#endif              /* defined(MKTIME) */

#if defined (MKTIME) || defined (TIMELOCAL)
    #ifdef TZSET
        #undef TZSET
    #endif
#endif              /* defined(MKTIME) || defined(TIMELOCAL) */

#if defined (MKTIME) || defined (TIMELOCAL) || defined (TZSET)
    #ifdef FTIME
        #undef FTIME
    #endif
#endif

#if defined (MKTIME) || defined (TIMELOCAL) || defined (TZSET) || defined (FTIME)
    #ifdef GETTIMEOFDAY
        #undef GETTIMEOFDAY
    #endif
#else
    #ifndef GETTIMEOFDAY
        #define GETTIMEOFDAY /* use gettimeofday() */
    #endif
#endif

/*
 * You may define as : #define TIMEZONE_HOOK		\ extern long
 * timezone ;	\ extern void tzset();
 */
#ifdef TIMEZONE_HOOK
TIMEZONE_HOOK
/* Which do you like better, `TIMEZONE_HOOK' or `TIMEZONE_HOOK;' ? */
#endif

#if defined (TZSET) && defined (_MINIX)
extern long timezone;           /* not defined in time.h */
#endif

/* ------------------------------------------------------------------------ */
#if defined (FTIME) || defined (GETTIMEOFDAY) || defined (TZSET)
static long
gettz()
    #ifdef TZSET
{
    tzset();
    return timezone;
}
    #endif

/* ------------------------------------------------------------------------ */
    #if !defined (TZSET) && defined (FTIME)
{
    struct timeb buf;

    ftime(&buf);
    return buf.timezone * 60L;
}
    #endif

/* ------------------------------------------------------------------------ */
    #if !defined (TZSET) && !defined (FTIME) /* maybe defined(GETTIMEOFDAY) */
{
    #ifdef HAVE_TM_ZONE
    time_t tt;

    time(&tt);
    return -localtime(&tt)->tm_gmtoff;
    #else /* HAVE_TM_ZONE */
    struct timeval tp;
    struct timezone tzp;
    gettimeofday(&tp, &tzp); /* specific to 4.3BSD */
    /*
     * return (tzp.tz_minuteswest * 60L + (tzp.tz_dsttime != 0 ? 60L *
     * 60L : 0));
     */
    return (tzp.tz_minuteswest * 60L);
    #endif /* HAVE_TM_ZONE */
}
    #endif
#endif              /* defined(FTIME) || defined(GETTIMEOFDAY) ||
                     * defined(TZSET) */

/* ------------------------------------------------------------------------ */
#ifdef NOT_USED
static struct tm*
msdos_to_unix_stamp_tm(a)
long a;
{
    static struct tm t;

    t.tm_sec = (a & 0x1f) * 2;
    t.tm_min = (a >> 5) & 0x3f;
    t.tm_hour = (a >> 11) & 0x1f;
    t.tm_mday = (a >> 16) & 0x1f;
    t.tm_mon = ((a >> 16 + 5) & 0x0f) - 1;
    t.tm_year = ((a >> 16 + 9) & 0x7f) + 80;
    return &t;
}
#endif

/* ------------------------------------------------------------------------ */
static time_t generic_to_unix_stamp(long t)
#if defined (MKTIME) || defined (TIMELOCAL)
{
    struct tm dostm;

    /*
     * special case:  if MSDOS format date and time were zero, then we
     * set time to be zero here too.
     */
    if (t == 0)
        return (time_t)0;

    dostm.tm_sec = (t & 0x1f) * 2;
    dostm.tm_min = t >> 5 & 0x3f;
    dostm.tm_hour = t >> 11 & 0x1f;
    dostm.tm_mday = t >> 16 & 0x1f;
    dostm.tm_mon = ((t >> 16) + 5 & 0x0f) - 1;  /* 0..11 */
    dostm.tm_year = ((t >> 16) + 9 & 0x7f) + 80;
    #if 0
    dostm.tm_isdst = 0; /* correct? */
    #endif
    dostm.tm_isdst = -1;    /* correct? */
    #ifdef MKTIME
    return (time_t)mktime(&dostm);
    #else           /* maybe defined(TIMELOCAL) */
    return (time_t)timelocal(&dostm);
    #endif
}

#else               /* defined(MKTIME) || defined(TIMELOCAL) */
{
    int year, month, day, hour, min, sec;
    long longtime;
    static uint dsboy[12] = { 0, 31, 59, 90, 120, 151,
                              181, 212, 243, 273, 304, 334 };
    uint days;

    /*
     * special case:  if MSDOS format date and time were zero, then we
     * set time to be zero here too.
     */
    if (t == 0)
        return (time_t)0;

    year = ((int)((t >> 16) + 9) & 0x7f) + 1980;
    month = (int)((t >> 16) + 5) & 0x0f;    /* 1..12 means Jan..Dec */
    day = (int)(t >> 16) & 0x1f;    /* 1..31 means 1st,...31st */

    hour = ((int)t >> 11) & 0x1f;
    min = ((int)t >> 5) & 0x3f;
    sec = ((int)t & 0x1f) * 2;

    /* Calculate days since 1970.01.01 */
    days = (365 * (year - 1970) +   /* days due to whole years */
        (year - 1970 + 1) / 4 +     /* days due to leap years */
        dsboy[month - 1] +     /* days since beginning of this year */
        day - 1);     /* days since beginning of month */

    if ((year % 4 == 0) &&
        (year % 100 != 0 || year % 400 == 0) &&     /* 1999.5.24 t.oka */
        (month >= 3))   /* if this is a leap year and month */
        days++;     /* is March or later, add a day */

    /* Knowing the days, we can find seconds */
    longtime = (((days * 24) + hour) * 60 + min) * 60 + sec;
    longtime += gettz();    /* adjust for timezone */

    /* LONGTIME is now the time in seconds, since 1970/01/01 00:00:00.  */
    return (time_t)longtime;
}
#endif              /* defined(MKTIME) || defined(TIMELOCAL) */

/* ------------------------------------------------------------------------ */
static long unix_to_generic_stamp(time_t t)
{
    struct tm* tm = localtime(&t);

    return ((((long)(tm->tm_year - 80)) << 25) +
        (((long)(tm->tm_mon + 1)) << 21) +
        (((long)tm->tm_mday) << 16) +
        (long)((tm->tm_hour << 11) +
            (tm->tm_min << 5) +
            (tm->tm_sec / 2)));
}

/* ------------------------------------------------------------------------ */
/* build header functions													*/
/* ------------------------------------------------------------------------ */
boolean get_header(struct zfile* fp, LzHeader* hdr)
{
    int header_size;
    int name_length;
    char data[LZHEADER_STRAGE];
    char dirname[FILENAME_LENGTH];
    int dir_length = 0;
    int checksum;
    int i;
    char* ptr;
    int extend_size;
    int dmy;

    bzero(hdr, sizeof(LzHeader));

    if (((header_size = zfile_getc(fp)) == EOF) || (header_size == 0))
    {
        return FALSE;   /* finish */
    }

    if (zfile_fread(data + I_HEADER_CHECKSUM,
            sizeof(char), header_size - 1, fp) < header_size - 1)
    {
        fatal_error(L"Invalid header (LHarc file ?)");
        return FALSE;   /* finish */
    }
    setup_get(data + I_HEADER_LEVEL);
    hdr->header_level = get_byte();
    if (hdr->header_level != 2 &&
        zfile_fread(data + header_size, sizeof(char), 2, fp) < 2)
    {
        fatal_error(L"Invalid header (LHarc file ?)");
        return FALSE;   /* finish */
    }

    if (hdr->header_level >= 3)
    {
        fatal_error(L"Unknown level header");
        return FALSE;
    }

    setup_get(data + I_HEADER_CHECKSUM);
    checksum = get_byte();

    if (hdr->header_level == 2)
    {
        hdr->header_size = header_size + checksum * 256;
    }
    else
    {
        hdr->header_size = header_size;
    }
    bcopy(data + I_METHOD, hdr->method, METHOD_TYPE_STRAGE);
    setup_get(data + I_PACKED_SIZE);
    hdr->packed_size = get_longword();
    hdr->original_size = get_longword();
    hdr->last_modified_stamp = get_longword();
    hdr->attribute = get_byte();

    if ((hdr->header_level = get_byte()) != 2)
    {
        if (calc_sum(data + I_METHOD, header_size) != checksum)
            warning(L"Checksum error (LHarc file?)", L"");
        name_length = get_byte();
        for (i = 0; i < name_length; i++)
            hdr->name[i] = (char)get_byte();
        hdr->name[name_length] = '\0';
    }
    else
    {
        hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
        name_length = 0;
    }

    /* defaults for other type */
    hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
    hdr->unix_gid = 0;
    hdr->unix_uid = 0;

    if (hdr->header_level == 0)
    {
        extend_size = header_size - name_length - 22;
        if (extend_size < 0)
        {
            if (extend_size == -2)
            {
                hdr->extend_type = EXTEND_GENERIC;
                hdr->has_crc = FALSE;
            }
            else
            {
                fatal_error(L"Unkonwn header (lha file?)");
                return FALSE;
            }
        }
        else
        {
            hdr->has_crc = TRUE;
            hdr->crc = get_word();
        }

        if (extend_size >= 1)
        {
            hdr->extend_type = get_byte();
            extend_size--;
        }
        if (hdr->extend_type == EXTEND_UNIX || hdr->extend_type == EXTEND_AMIGAOS)
        {
            if (extend_size >= 11)
            {
                hdr->minor_version = get_byte();
                hdr->unix_last_modified_stamp = (time_t)get_longword();
                hdr->unix_mode = get_word();
                hdr->unix_uid = get_word();
                hdr->unix_gid = get_word();
                extend_size -= 11;
            }
            else
            {
                hdr->extend_type = EXTEND_GENERIC;
            }
        }
        while (extend_size-- > 0)
            dmy = get_byte();
        if (hdr->extend_type == EXTEND_UNIX)
            return TRUE;
    }
    else if (hdr->header_level == 1)
    {
        hdr->has_crc = TRUE;
        extend_size = header_size - name_length - 25;
        hdr->crc = get_word();
        hdr->extend_type = get_byte();
        while (extend_size-- > 0)
            dmy = get_byte();
    }
    else     /* level 2 */
    {
        hdr->has_crc = TRUE;
        hdr->crc = get_word();
        hdr->extend_type = get_byte();
    }

    if (hdr->header_level > 0)
    {
        /* Extend Header */
        if (hdr->header_level != 2)
            setup_get(data + hdr->header_size);
        ptr = get_ptr;
        while ((header_size = get_word()) != 0)
        {
            if (hdr->header_level != 2 &&
                ((data + LZHEADER_STRAGE - get_ptr < header_size) ||
                    zfile_fread(get_ptr, sizeof(char), header_size, fp) < header_size))
            {
                fatal_error(L"Invalid header (LHa file ?)");
                return FALSE;
            }
            switch (get_byte())
            {
                case 0:
                    /*
                     * header crc
                     */
                    setup_get(get_ptr + header_size - 3);
                    break;
                case 1:
                    /*
                     * filename
                     */
                    if (header_size >= 256)
                        return FALSE;
                    for (i = 0; i < header_size - 3; i++)
                        hdr->name[i] = (char)get_byte();
                    hdr->name[header_size - 3] = '\0';
                    name_length = header_size - 3;
                    break;
                case 2:
                    /*
                     * directory
                     */
                    if (header_size >= FILENAME_LENGTH)
                        return FALSE;
                    for (i = 0; i < header_size - 3; i++)
                        dirname[i] = (char)get_byte();
                    dirname[header_size - 3] = '\0';
                    convdelim((unsigned char*)dirname, DELIM);
                    dir_length = header_size - 3;
                    break;
                case 0x40:
                    /*
                     * MS-DOS attribute
                     */
                    if (hdr->extend_type == EXTEND_MSDOS ||
                    hdr->extend_type == EXTEND_HUMAN ||
                    hdr->extend_type == EXTEND_AMIGAOS ||
                    hdr->extend_type == EXTEND_GENERIC)
                        hdr->attribute = get_word();
                    break;
                case 0x50:
                    /*
                     * UNIX permission
                     */
                    if (hdr->extend_type == EXTEND_UNIX)
                        hdr->unix_mode = get_word();
                    break;
                case 0x51:
                    /*
                     * UNIX gid and uid
                     */
                    if (hdr->extend_type == EXTEND_UNIX)
                    {
                        hdr->unix_gid = get_word();
                        hdr->unix_uid = get_word();
                    }
                    break;
                case 0x52:
                    /*
                     * UNIX group name
                     */
                    setup_get(get_ptr + header_size - 3);
                    break;
                case 0x53:
                    /*
                     * UNIX user name
                     */
                    setup_get(get_ptr + header_size - 3);
                    break;
                case 0x54:
                    /*
                     * UNIX last modified time
                     */
                    if (hdr->extend_type == EXTEND_UNIX || hdr->extend_type == EXTEND_AMIGAOS)
                        hdr->unix_last_modified_stamp = (time_t)get_longword();
                    break;
                default:
                    /*
                     * other headers
                     */
                    setup_get(get_ptr + header_size - 3);
                    break;
            }
        }
        if (hdr->header_level != 2 && get_ptr - ptr != 2)
        {
            hdr->packed_size -= get_ptr - ptr - 2;
            hdr->header_size += get_ptr - ptr - 2;
        }
    }

    switch (hdr->extend_type)
    {
        case EXTEND_MSDOS:
            msdos_to_unix_filename(hdr->name, name_length);
            msdos_to_unix_filename(dirname, dir_length);
        case EXTEND_HUMAN:
            if (hdr->header_level == 2)
                hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
            else
                hdr->unix_last_modified_stamp =
                generic_to_unix_stamp(hdr->last_modified_stamp);
            break;

            #ifdef OSK
        case EXTEND_OS68K:
        case EXTEND_XOSK:
            #endif
        case EXTEND_AMIGAOS:
        case EXTEND_UNIX:
            break;

        case EXTEND_MACOS:
            macos_to_unix_filename(hdr->name, name_length);
            /* macos_to_unix_filename(dirname, dir_length); */
            hdr->unix_last_modified_stamp =
            generic_to_unix_stamp(hdr->last_modified_stamp);
            break;

        default:
            generic_to_unix_filename(hdr->name, name_length);
            generic_to_unix_filename(dirname, dir_length);
            if (hdr->header_level == 2)
                hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
            else
                hdr->unix_last_modified_stamp =
                generic_to_unix_stamp(hdr->last_modified_stamp);
    }

    if (dir_length)
    {
        strcat(dirname, hdr->name);
        strcpy(hdr->name, dirname);
        name_length += dir_length;
    }

    return TRUE;
}

/* ------------------------------------------------------------------------ */
void init_header(char* name, struct stat* v_stat, LzHeader* hdr)
{
    int len;

    if (compress_method == LZHUFF5_METHOD_NUM)  /* Changed N.Watazaki */
        bcopy(LZHUFF5_METHOD, hdr->method, METHOD_TYPE_STRAGE);
    else if (compress_method)
        bcopy(LZHUFF1_METHOD, hdr->method, METHOD_TYPE_STRAGE);
    else
        bcopy(LZHUFF0_METHOD, hdr->method, METHOD_TYPE_STRAGE);

    hdr->packed_size = 0;
    hdr->original_size = v_stat->st_size;
    hdr->last_modified_stamp = unix_to_generic_stamp(v_stat->st_mtime);
    hdr->attribute = GENERIC_ATTRIBUTE;
    hdr->header_level = header_level;
    strcpy(hdr->name, name);
    len = strlen(name);
    hdr->crc = 0x0000;
    hdr->extend_type = EXTEND_UNIX;
    hdr->unix_last_modified_stamp = v_stat->st_mtime;
    /* since 00:00:00 JAN.1.1970 */
    #ifdef NOT_COMPATIBLE_MODE
    /* Please need your modification in this space. */
    #else
    hdr->unix_mode = v_stat->st_mode;
    #endif

    hdr->unix_uid = v_stat->st_uid;
    hdr->unix_gid = v_stat->st_gid;

    if (is_directory(v_stat))
    {
        bcopy(LZHDIRS_METHOD, hdr->method, METHOD_TYPE_STRAGE);
        hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
        hdr->original_size = 0;
        if (len > 0 && hdr->name[len - 1] != '/')
            strcpy(&hdr->name[len++], "/");
    }

    #ifdef S_IFLNK
    if (is_symlink(v_stat))
    {
        char lkname[257];
        int len;
        bcopy(LZHDIRS_METHOD, hdr->method, METHOD_TYPE_STRAGE);
        hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
        hdr->original_size = 0;
        len = readlink(name, lkname, 256);
        lkname[len] = (char)'\0';
        sprintf(hdr->name, "%s|%s", hdr->name, lkname);
    }
    #endif
    if (generic_format)
        unix_to_generic_filename(hdr->name, len);
}